#include "XIO.hpp"
#include <common.hpp>
#include "../Material.hpp"
#include <Utility/FileTools.hpp>
#include <Utility/BraceFile.hpp>

namespace zzz{

void EatUntil(istringstream &iss, char c)
{
  char tmpchar;
  do
  {
    iss>>tmpchar;
    if (iss.fail()) return;
  }while (tmpchar!=c);
}

bool XIO::Load(const string &filename, TriMesh &mesh, bool load_mat)
{
  BraceFile bf("{}");
  if (!bf.LoadFile(filename)) return false;

  BraceNode meshNode=bf.GetFirstNode("Mesh");
  if (!meshNode.IsValid()) return false;

  mesh.Clear();
  int npos,nface;
  string data;
  Vector3f v;
  Vector3i vi;

  vector<Vector3i> facep,facen;

  //position
  meshNode.GetChildrenText(data);
  {
    istringstream iss(data);
    iss>>npos;
    EatUntil(iss,';');
    for (int i=0; i<npos; i++)
    {
      iss>>v[0];
      EatUntil(iss,';');
      iss>>v[1];
      EatUntil(iss,';');
      iss>>v[2];
      EatUntil(iss,';');
      if (i<npos-1) EatUntil(iss,',');
      else EatUntil(iss,';');
      mesh.pos_.push_back(v);
      mesh.AABB_+=v;
    }
    mesh.SetFlag(MESH_POS);

    iss>>nface;
    EatUntil(iss,';');
    for (int i=0; i<nface; i++)
    {
      EatUntil(iss,'3');
      EatUntil(iss,';');
      iss>>vi[0];
      EatUntil(iss,',');
      iss>>vi[1];
      EatUntil(iss,',');
      iss>>vi[2];
      EatUntil(iss,';');
      if (i<nface-1) EatUntil(iss,',');
      else EatUntil(iss,';');
      facep.push_back(vi);
    }
  }

  //normal
  BraceNode normalNode=meshNode.GetFirstNode("MeshNormals");
  if (normalNode.IsValid())
  {
    normalNode.GetChildrenText(data);
    istringstream iss(data);
    iss>>npos;
    EatUntil(iss,';');
    for (int i=0; i<npos; i++)
    {
      iss>>v[0];
      EatUntil(iss,';');
      iss>>v[1];
      EatUntil(iss,';');
      iss>>v[2];
      EatUntil(iss,';');
      if (i<npos-1) EatUntil(iss,',');
      else EatUntil(iss,';');
      mesh.nor_.push_back(v);
    }
    mesh.SetFlag(MESH_NOR);

    iss>>nface;
    EatUntil(iss,';');
    for (int i=0; i<nface; i++)
    {
      EatUntil(iss,'3');
      EatUntil(iss,';');
      iss>>vi[0];
      EatUntil(iss,',');
      iss>>vi[1];
      EatUntil(iss,',');
      iss>>vi[2];
      EatUntil(iss,';');
      if (i<nface-1) EatUntil(iss,',');
      else EatUntil(iss,';');
      facen.push_back(vi);
    }
  }

  //texcoord
  BraceNode texNode=meshNode.GetFirstNode("MeshTextureCoords");
  if (texNode.IsValid())
  {
    texNode.GetChildrenText(data);
    istringstream iss(data);
    iss>>npos;
    EatUntil(iss,';');
    v[2]=0;
    for (int i=0; i<npos; i++)
    {
      iss>>v[0];
      EatUntil(iss,';');
      iss>>v[1];
      EatUntil(iss,';');
      if (i<npos-1) EatUntil(iss,',');
      else EatUntil(iss,';');
      mesh.tex_.push_back(v);
    }
    mesh.SetFlag(MESH_TEX);
  }

  //mat group
  BraceNode matNode=meshNode.GetFirstNode("MeshMaterialList");
  if (matNode.IsValid())
  {
    matNode.GetChildrenText(data);
    istringstream iss(data);
    int nmat;
    iss>>nmat;
    EatUntil(iss,';');
    for (int i=0; i<nmat; i++)
      mesh.groups_.push_back(new TriMesh::MeshGroup());
    iss>>nface;
    EatUntil(iss,';');
    int facem;
    for (int i=0; i<nface; i++)
    {
      iss>>facem;
      if (i<nface-1) EatUntil(iss,',');
      else EatUntil(iss,';');
      mesh.groups_[facem]->facep_.push_back(facep[i]);
      if (!facen.empty()) mesh.groups_[facem]->facen_.push_back(facen[i]);
    }
    for (int i=0; i<nmat; i++)
    {
      mesh.groups_[i]->SetFlag(MESH_POS);
      if (!facen.empty()) mesh.groups_[i]->SetFlag(MESH_NOR);
      if (mesh.HasFlag(MESH_TEX)) 
      {
        mesh.groups_[i]->facet_=mesh.groups_[i]->facep_;
        mesh.groups_[i]->SetFlag(MESH_TEX);
        mesh.groups_[i]->matname_="Material";
        mesh.groups_[i]->matname_<<i;
      }
    }

    //read material
    for (int i=0; i<nmat; i++)
    {
      BraceNode materialNode=matNode.GetNode(i);
      materialNode.GetChildrenText(data);
      istringstream iss(data);
      Material *mat=new Material;
      ZRM->Add(mat,mesh.groups_[i]->matname_.c_str());
      iss>>mat->ambient_[0];EatUntil(iss,';');
      iss>>mat->ambient_[1];EatUntil(iss,';');
      iss>>mat->ambient_[2];EatUntil(iss,';');
      iss>>mat->ambient_[3];EatUntil(iss,';');EatUntil(iss,';');
      mat->diffuse_=mat->ambient_;
      iss>>mat->shininess_;EatUntil(iss,';');
      iss>>mat->specular_[0];EatUntil(iss,';');
      iss>>mat->specular_[1];EatUntil(iss,';');
      iss>>mat->specular_[2];EatUntil(iss,';');EatUntil(iss,';');
      iss>>mat->emission_[0];EatUntil(iss,';');
      iss>>mat->emission_[1];EatUntil(iss,';');
      iss>>mat->emission_[2];EatUntil(iss,';');EatUntil(iss,';');
      mat->SetFlag(MAT_AMB);
      mat->SetFlag(MAT_DIF);
      mat->SetFlag(MAT_SPE);
      mat->SetFlag(MAT_EMI);

      BraceNode textureNode=materialNode.GetFirstNode("TextureFilename");
      if (textureNode.IsValid())
      {
        string texfilename=textureNode.GetFirstNode().GetText();
        texfilename = Trim(texfilename);
        texfilename = TrimBack(texfilename, ";");
        texfilename = Trim(texfilename,"\"");
        if (!texfilename.empty())
        {
          //BUG: path bug
          if (GLExists())
          {
            mat->diffuseTex_.FileToTexture<Vector3uc>(texfilename.c_str());
            mat->SetFlag(MAT_DIFTEX);
          }
          else
            mat->diffuseTexName_=texfilename;
        }
      }
    }
  }
  else
  {
    //only one group
    mesh.groups_.push_back(new TriMesh::MeshGroup());
    TriMesh::MeshGroup *group=mesh.groups_.back();
    group->facep_=facep;
    group->SetFlag(MESH_POS);
    if (mesh.HasFlag(MESH_NOR)) {group->facen_=facen;group->SetFlag(MESH_NOR);}
    if (mesh.HasFlag(MESH_TEX)) {group->facet_=facep;group->SetFlag(MESH_TEX);}
  }
  return true;
}

bool XIO::Save(const string &filename, TriMesh &mesh, bool save_mat)
{
  BraceFile bf;
  //head
  bf.AppendNode("xof 0303txt 0032");
  bf.AppendNode("template Vector",'{','}')<<"<3d82ab5e-62da-11cf-ab39-0020af71e433>"<<"FLOAT x; "<<"FLOAT y; "<<"FLOAT z; ";
  bf.AppendNode("template MeshFace",'{','}')<<"<3d82ab5f-62da-11cf-ab39-0020af71e433>"<<"DWORD nFaceVertexIndices; "<<"array DWORD faceVertexIndices[nFaceVertexIndices]; ";
  bf.AppendNode("template Mesh",'{','}')<<"<3d82ab44-62da-11cf-ab39-0020af71e433>"<<"DWORD nVertices; "<<"array Vector vertices[nVertices]; "<<"DWORD nFaces; "<<"array MeshFace faces[nFaces]; "<<"[...]";
  bf.AppendNode("template MeshNormals",'{','}')<<"<f6f23f43-7686-11cf-8f52-0040333594a3>"<<"DWORD nNormals; "<<"array Vector normals[nNormals]; "<<"DWORD nFaceNormals; "<<"array MeshFace faceNormals[nFaceNormals]; ";
  bf.AppendNode("template Coords2d",'{','}')<<"<f6f23f44-7686-11cf-8f52-0040333594a3>"<<"FLOAT u; "<<"FLOAT v; ";
  bf.AppendNode("template MeshTextureCoords",'{','}')<<"<f6f23f40-7686-11cf-8f52-0040333594a3>"<<"DWORD nTextureCoords; "<<"array Coords2d textureCoords[nTextureCoords]; ";
  bf.AppendNode("template ColorRGBA",'{','}')<<"<35ff44e0-6c7c-11cf-8f52-0040333594a3>"<<"FLOAT red; "<<"FLOAT green; "<<"FLOAT blue; "<<"FLOAT alpha; ";
  bf.AppendNode("template ColorRGB",'{','}')<<"<d3e16e81-7835-11cf-8f52-0040333594a3>"<<"FLOAT red; "<<"FLOAT green; "<<"FLOAT blue; ";
  bf.AppendNode("template Material",'{','}')<<"<3d82ab4d-62da-11cf-ab39-0020af71e433>"<<"ColorRGBA faceColor; "<<"FLOAT power; "<<"ColorRGB specularColor; "<<"ColorRGB emissiveColor; "<<"[...]";
  bf.AppendNode("template MeshMaterialList",'{','}')<<"<f6f23f42-7686-11cf-8f52-0040333594a3>"<<"DWORD nMaterials; "<<"DWORD nFaceIndexes; "<<"array DWORD faceIndexes[nFaceIndexes]; "<<"[Material <3d82ab4d-62da-11cf-ab39-0020af71e433>]";
  bf.AppendNode("template TextureFilename",'{','}')<<"<a42790e1-7810-11cf-8f52-0040333594a3>"<<"STRING filename; ";

  mesh.CreateElement();
  BraceNode meshNode=bf.AppendNode("Mesh",'{','}');
  zuint nface=0;
  for (zuint i=0; i<mesh.groups_.size(); i++) nface+=mesh.groups_[i]->eindex_.size();
  zuint npos=mesh.epos_.size();
  //position
  {
    meshNode<<ToString(npos)+"; ";
    for (zuint i=0; i<mesh.epos_.size(); i++)
    {
      ostringstream oss;
      oss.precision(6);
      oss<<fixed;
      oss<<mesh.epos_[i][0]<<';'<<mesh.epos_[i][1]<<';'<<mesh.epos_[i][2]<<';';
      if (i<npos-1) oss<<',';
      else oss<<';';
      meshNode<<oss.str();
    }

    meshNode<<ToString(nface)+"; ";
    for (zuint i=0; i<mesh.groups_.size(); i++) for (zuint j=0; j<mesh.groups_[i]->eindex_.size(); j++)
    {
      ostringstream oss;
      oss<<"3; "<<mesh.groups_[i]->eindex_[j][0]<<','<<mesh.groups_[i]->eindex_[j][1]<<','<<mesh.groups_[i]->eindex_[j][2]<<';';
      if (i!=mesh.groups_.size()-1 || j!=mesh.groups_.back()->eindex_.size()-1) oss<<',';
      else oss<<';';
      meshNode<<oss.str();
    }
  }

  //normal
  if (mesh.HasFlag(MESH_NOR))
  {
    BraceNode normalNode=meshNode.AppendNode("MeshNormals",'{','}');
    normalNode<<ToString(npos)+"; ";
    for (zuint i=0; i<mesh.enor_.size(); i++)
    {
      ostringstream oss;
      oss.precision(6);
      oss<<fixed;
      oss<<mesh.enor_[i][0]<<';'<<mesh.enor_[i][1]<<';'<<mesh.enor_[i][2]<<';';
      if (i<npos-1) oss<<',';
      else oss<<';';
      normalNode<<oss.str();
    }

    normalNode<<ToString(nface)+"; ";
    for (zuint i=0; i<mesh.groups_.size(); i++) for (zuint j=0; j<mesh.groups_[i]->eindex_.size(); j++)
    {
      ostringstream oss;
      oss<<"3; "<<mesh.groups_[i]->eindex_[j][0]<<','<<mesh.groups_[i]->eindex_[j][1]<<','<<mesh.groups_[i]->eindex_[j][2]<<';';
      if (i!=mesh.groups_.size()-1 || j!=mesh.groups_.back()->eindex_.size()-1) oss<<',';
      else oss<<';';
      normalNode<<oss.str();
    }
  }

  //texcoord
  if (mesh.HasFlag(MESH_TEX))
  {
    BraceNode texNode=meshNode.AppendNode("MeshTextureCoords",'{','}');
    texNode<<ToString(npos)+"; ";
    for (zuint i=0; i<mesh.etex_.size(); i++)
    {
      ostringstream oss;
      oss.precision(6);
      oss<<fixed;
      oss<<mesh.etex_[i][0]<<';'<<mesh.etex_[i][1]<<';';
      if (i<npos-1) oss<<',';
      else oss<<';';
      texNode<<oss.str();
    }
  }

  //mat group
  BraceNode matNode=meshNode.AppendNode("MeshMaterialList",'{','}');
  matNode<<ToString(mesh.groups_.size())+"; ";
  matNode<<ToString(nface)+"; ";
  for (zuint i=0; i<mesh.groups_.size(); i++)
  {
    for (zuint j=0; j<mesh.groups_[i]->eindex_.size(); j++)
    {
      ostringstream oss;
      oss<<i;
      if (i!=mesh.groups_.size()-1 || j!=mesh.groups_.back()->eindex_.size()-1)
        oss<<",";
      else
        oss<<"; ";
      matNode<<oss.str();
    }
  }

  //material
  for (zuint i=0; i<mesh.groups_.size(); i++)
  {
    BraceNode materialNode=matNode.AppendNode("Material",'{','}');
    if (mesh.groups_[i]->matname_.empty())
    {
      materialNode<<"0.8; 0.8; 0.8; 0; ;"<<"0; "<<"1; 1; 1; ;"<<"0; 0; 0; ;";
    }
    else
    {
      Material *mat=ZRM->Get<Material*>(mesh.groups_[i]->matname_.c_str());
      string line;
      if (mat->HasFlag(MAT_AMB)) line<<mat->ambient_.r()<<"; "<<mat->ambient_.g()<<"; "<<mat->ambient_.b()<<"; "<<mat->ambient_.a()<<"; ;";
      else if (mat->HasFlag(MAT_DIF)) line<<mat->diffuse_.r()<<"; "<<mat->diffuse_.g()<<"; "<<mat->diffuse_.b()<<"; "<<mat->diffuse_.a()<<"; ;";
      else line="0.8; 0.8; 0.8; 0; ;";
      materialNode<<line;
      line.clear();
      if (mat->HasFlag(MAT_SPE))
      {
        materialNode<<ToString(mat->shininess_)+"; ";
        line<<mat->specular_.r()<<"; "<<mat->specular_.g()<<"; "<<mat->specular_.b()<<"; ;";
        materialNode<<line;
      }
      else
        materialNode<<"0; "<<"1; 1; 1; ;";
      
      line.clear();
      if (mat->HasFlag(MAT_EMI))
      {
        line<<mat->emission_.r()<<"; "<<mat->emission_.g()<<"; "<<mat->emission_.b()<<"; ;";
        materialNode<<line;
      }
      else
        materialNode<<"1; 1; 1; ;";
      
      if (mat->HasFlag(MAT_DIFTEX))
      {
        BraceNode textureNode=matNode.AppendNode("TextureFilename",'{','}');
        string storefilename=GetBase(filename);
        storefilename<<'_'<<mesh.groups_[i]->matname_<<"_dif.png";
        mat->diffuseTex_.TextureToFile<Vector4uc>(storefilename.c_str());
        line="\"";
        line<<storefilename<<"\"; ";
        textureNode<<line;
      }
    }
  }
  if (!bf.SaveFile(filename," ")) return false;
  return true;
}

}